﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
using System.Data;
using AirLinq.Reflection;
using AirLinq.Meta;
using System.Runtime.CompilerServices;

namespace AirLinq.Dynamic {
	public class CodeGenerator {
		private class GeneratedTypeDescription {
			private readonly Type _type;
			private readonly ConstructorInfo _constructor;
			public GeneratedTypeDescription(Type type, ConstructorInfo constructor) {
				_type = type;
				_constructor = constructor;
			}
			public Type Type {
				get {
					return _type;
				}
			}
			public ConstructorInfo Constructor {
				get {
					return _constructor;
				}
			}
		}
		private static ConstructorInfo OCtor = ReflectionHelper.GetBodyConstructor(() => new object());

		private static MethodInfo RGetBoolean = ReflectionHelper.GetBodyMethod<IDataRecord, bool>(row => row.GetBoolean(0));
		private static MethodInfo RGetByte = ReflectionHelper.GetBodyMethod<IDataRecord, byte>(row => row.GetByte(0));
		private static MethodInfo RGetChar = ReflectionHelper.GetBodyMethod<IDataRecord, char>(row => row.GetChar(0));
		private static MethodInfo RGetDateTime = ReflectionHelper.GetBodyMethod<IDataRecord, DateTime>(row => row.GetDateTime(0));
		private static MethodInfo RGetDecimal = ReflectionHelper.GetBodyMethod<IDataRecord, decimal>(row => row.GetDecimal(0));
		private static MethodInfo RGetDouble = ReflectionHelper.GetBodyMethod<IDataRecord, double>(row => row.GetDouble(0));
		private static MethodInfo RGetFloat = ReflectionHelper.GetBodyMethod<IDataRecord, float>(row => row.GetFloat(0));
		private static MethodInfo RGetGuid = ReflectionHelper.GetBodyMethod<IDataRecord, Guid>(row => row.GetGuid(0));
		private static MethodInfo RGetInt16 = ReflectionHelper.GetBodyMethod<IDataRecord, short>(row => row.GetInt16(0));
		private static MethodInfo RGetInt32 = ReflectionHelper.GetBodyMethod<IDataRecord, int>(row => row.GetInt32(0));
		private static MethodInfo RGetInt64 = ReflectionHelper.GetBodyMethod<IDataRecord, long>(row => row.GetInt64(0));
		private static MethodInfo RGetString = ReflectionHelper.GetBodyMethod<IDataRecord, string>(row => row.GetString(0));
		// bool IsDBNull(int i);

		private static readonly CodeGenerator _instance = new CodeGenerator();
		public static CodeGenerator Instance {
			get {
				return _instance;
			}
		}
		private CodeGenerator() { }

		private ModuleBuilder _moduleBuilder;
		private readonly object _sync = new object();
		private readonly Dictionary<Type, ReaderFactory> _readerFactories = new Dictionary<Type, ReaderFactory>();

		private ModuleBuilder GetModuleBuilder() {
			if (_moduleBuilder == null) {
				lock (_sync) {
					if (_moduleBuilder == null) {
						var name = new AssemblyName("AirLinqDynamic");
						var builder = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run);
						_moduleBuilder = builder.DefineDynamicModule("AirModel");
					}
				}
			}
			return _moduleBuilder;
		}
		private PropertyInfo[] GetPropertiesToImplement(Type iface) {

			var allProperties = new HashSet<PropertyInfo>(iface.GetProperties());

			foreach (var item in iface.GetInterfaces()) {
				allProperties.UnionWith(GetPropertiesToImplement(item));
			}
			return allProperties.ToArray();
		}
		private GeneratedTypeDescription GenerateProxyTypeForInterface(Type iface) {
			if (!iface.IsInterface) {
				throw new ArgumentException("iface must be interface");
			}
			var cls = GetModuleBuilder().DefineType(string.Format("{0}_Impl", iface.FullName), TypeAttributes.Class | TypeAttributes.BeforeFieldInit | TypeAttributes.Public, null, new[] { iface });
			var propsToImplement = GetPropertiesToImplement(iface);
			var fields = new List<FieldInfo>();
			for (var i = 0; i < propsToImplement.Length; i++) {
				var fldName = "_" + propsToImplement[i].Name;
				var propToImpl = propsToImplement[i];
				var fld = cls.DefineField(fldName, propToImpl.PropertyType, null, null, FieldAttributes.Private);
				fields.Add(fld);
				var prop = cls.DefineProperty(propToImpl.Name, propToImpl.Attributes, CallingConventions.HasThis, propToImpl.PropertyType, null, null, null, null, null);
				var getter = cls.DefineMethod(propToImpl.GetGetMethod().Name, GetOverrideMethodAttributes(propToImpl.GetGetMethod()), propToImpl.PropertyType, null);
				var il = getter.GetILGenerator();

				il.Emit(OpCodes.Ldarg_0);
				il.Emit(OpCodes.Ldfld, fld);
				il.Emit(OpCodes.Ret);

				prop.SetGetMethod(getter);

			}
			//TODO generate constructor with plain number of parameters (not IDataRecord)
			var constr = cls.DefineConstructor(MethodAttributes.Public | MethodAttributes.HideBySig, CallingConventions.HasThis, new[] { typeof(IDataRecord) });
			var rowParam = constr.DefineParameter(1, System.Reflection.ParameterAttributes.None, "row");
			var ilb = constr.GetILGenerator();

			ilb.Emit(OpCodes.Ldarg_0);
			ilb.Emit(OpCodes.Call, OCtor);

			ilb.Emit(OpCodes.Ldarg_0);
			ilb.Emit(OpCodes.Ldarg_1);
			ilb.Emit(OpCodes.Ldc_I4_0);
			ilb.Emit(OpCodes.Callvirt, RGetInt16);
			ilb.Emit(OpCodes.Stfld, fields[0]);
			ilb.Emit(OpCodes.Ret);
			return new GeneratedTypeDescription(cls.CreateType(), constr);
		}

		private MethodAttributes GetOverrideMethodAttributes(MethodInfo methodInfo) {
			return methodInfo.Attributes & ~MethodAttributes.Abstract | MethodAttributes.Final;
		}
		private Type GenerateMaterializerTypeForInterface(Type iface, ConstructorInfo objectConstructor) {
			if (!iface.IsInterface) {
				throw new ArgumentException("iface must be interface");
			}
			var materializerType = typeof(IMaterializer<>).MakeGenericType(iface);
			var cls = GetModuleBuilder().DefineType(
				string.Format("Materializer_for_{0}", iface.Name),
				TypeAttributes.Class | TypeAttributes.BeforeFieldInit | TypeAttributes.Public,
				null,
				new[] { materializerType });
			GenerateEmptyContructor(cls);
			GenerateCreateMethod(cls, materializerType, objectConstructor);
			return cls.CreateType();
		}
		private void GenerateCreateMethod(TypeBuilder cls, Type materializerType, ConstructorInfo objectContrsuctor) {
			var baseMethod = materializerType.GetMethod("Create");
			var parameter = baseMethod.GetParameters()[0];
			var parameterTypes = new[] { parameter.ParameterType };
			var overrideMethod = cls.DefineMethod(baseMethod.Name, GetOverrideMethodAttributes(baseMethod), baseMethod.ReturnType, parameterTypes);
			overrideMethod.DefineParameter(1, parameter.Attributes, parameter.Name);
			var il = overrideMethod.GetILGenerator();
			il.Emit(OpCodes.Ldarg_1);
			il.Emit(OpCodes.Newobj, objectContrsuctor);
			il.Emit(OpCodes.Ret);
		}
		private void GenerateEmptyContructor(TypeBuilder cls) {
			var constructor = cls.DefineConstructor(MethodAttributes.Public | MethodAttributes.HideBySig, CallingConventions.HasThis, new Type[0]);

			var il = constructor.GetILGenerator();

			il.Emit(OpCodes.Ldarg_0);
			il.Emit(OpCodes.Call, OCtor);
			il.Emit(OpCodes.Ret);

		}

		public ReaderFactory GetReaderFactory(Type type) {
			ReaderFactory factory;
			if (!_readerFactories.TryGetValue(type, out factory)) {
				lock (_sync) {
					if (!_readerFactories.TryGetValue(type, out factory)) {
						factory = CreateReaderFactoryForType(type);
						_readerFactories[type] = factory;
					}
				}
			}
			return factory;
		}
		private ReaderFactory CreateReaderFactoryForType(Type type) {
			if (type.IsInterface) {
				return CreateReaderFactoryForInterface(type);
			} else if (IsAnonymouseType(type)) {
				return CreateReaderFactoryForAnonymouse(type);
			}
			throw new NotImplementedException("codegeneration for not anonymouse and not interface is not implemented");

		}

		private ReaderFactory CreateReaderFactoryForAnonymouse(Type type) {
			var materializertype = typeof(AnonymouseMaterializer<>).MakeGenericType(type);
			var materializerInstance = Activator.CreateInstance(materializertype, type.GetProperties().Length);
			var factoryType = typeof(ReaderFactory<>).MakeGenericType(type);
			return (ReaderFactory) Activator.CreateInstance(factoryType, materializerInstance);
		}

		private bool IsAnonymouseType(Type type) {
			var attributes = type.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false);
			return attributes != null && attributes.Length > 0;

		}

		private ReaderFactory CreateReaderFactoryForInterface(Type type) {
			var factoryType = typeof(ReaderFactory<>).MakeGenericType(type);

			var implementType = GenerateProxyTypeForInterface(type);

			var materializerType = GenerateMaterializerTypeForInterface(type, implementType.Constructor);
			var materializerInstance = Activator.CreateInstance(materializerType);
			return (ReaderFactory)Activator.CreateInstance(factoryType, materializerInstance);
		}
	

		

		private void GenerateCreateMethod2(TypeBuilder cls, Type materializerType, ConstructorInfo constructor) {
			var baseMethod = materializerType.GetMethod("Create");
			var parameter = baseMethod.GetParameters()[0];
			var parameterTypes = new []{parameter.ParameterType};
			var overrideMethod = cls.DefineMethod(baseMethod.Name, GetOverrideMethodAttributes(baseMethod), baseMethod.ReturnType, parameterTypes);
			overrideMethod.DefineParameter(1, parameter.Attributes, parameter.Name);

			var il = overrideMethod.GetILGenerator();
			var constructorParameters = constructor.GetParameters();
			for (var i = 0; i < constructorParameters.Length; i++) {
				EmitPassReaderArgument(il, i, constructorParameters[i]);
			}
			
			il.Emit(OpCodes.Newobj, constructor);
			il.Emit(OpCodes.Ret);
		}
		private void EmitLoadInt(ILGenerator il, int num){
			switch (num) {
				case 0:
					il.Emit(OpCodes.Ldc_I4_0);
					break;
				case 1:
					il.Emit(OpCodes.Ldc_I4_1);
					break;
				case 2:
					il.Emit(OpCodes.Ldc_I4_2);
					break;
				case 3:
					il.Emit(OpCodes.Ldc_I4_3);
					break;
				case 4:
					il.Emit(OpCodes.Ldc_I4_4);
					break;
				case 5:
					il.Emit(OpCodes.Ldc_I4_5);
					break;
				case 6:
					il.Emit(OpCodes.Ldc_I4_6);
					break;
				case 7:
					il.Emit(OpCodes.Ldc_I4_7);
					break;
				case 8:
					il.Emit(OpCodes.Ldc_I4_8);
					break;
				default:
					if (num <= 0xff) {
						il.Emit(OpCodes.Ldc_I4_S, (byte)num);
					} else {
						il.Emit(OpCodes.Ldc_I4, num);
					}
					break;
			}
		}
		private void EmitPassReaderArgument(ILGenerator il, int index, ParameterInfo parameterInfo) {
			il.Emit(OpCodes.Ldarg_1);
			EmitLoadInt(il,index);
			var methodInfo = GetMethodForParameterType(parameterInfo.ParameterType);
			il.Emit(OpCodes.Callvirt, methodInfo);

		}
		private MethodInfo GetMethodForParameterType(Type type) {
			if (typeof(bool).Equals(type)) {
				return RGetBoolean;
			}
			if (typeof(byte).Equals(type)) {
				return RGetByte;
			}
			if (typeof(char).Equals(type)) {
				return RGetChar;
			}
			if (typeof(DateTime).Equals(type)) {
				return RGetDateTime;
			}
			if (typeof(decimal).Equals(type)) {
				return RGetDecimal;
			}
			if (typeof(double).Equals(type)) {
				return RGetDouble;
			}
			if (typeof(float).Equals(type)) {
				return RGetFloat;
			}
			if (typeof(Guid).Equals(type)) {
				return RGetGuid;
			}
			if (typeof(short).Equals(type)) {
				return RGetInt16;
			}
			if (typeof(int).Equals(type)) {
				return RGetInt32;
			}
			if (typeof(long).Equals(type)) {
				return RGetInt64;
			}
			if (typeof(string).Equals(type)) {
				return RGetString;
			}
			throw new NotImplementedException("not implemented type");
		}
	}
}